import os
import sqlite3
from collections import defaultdict
import textwrap
import cPickle

from wx import DateTime

from genutils.list_dict import List_dict

from projects.electrophysiology.stimuli.helper_codes.general_stimulus import setup_window
from projects.electrophysiology.stimuli.helper_codes.Eye_cup import Eye_cup
from projects.electrophysiology.stimuli.helper_codes.RF import RF
from projects.electrophysiology.view_data.frame_utils import format_frames
from projects.electrophysiology.view_data.file_utils import read_wessel_text_file

ddt = DateTime()

def format_JDN(JDN, format):
    ''' 
    This function formats a Julian Day Number, JDN as a
       format appropriate string.
    Inputs:
        JDN         : The Julian Day Number (a float)
        format      : The kind of string output you want,
                      can be 'date' or 'time' or a string that is supported
                      by wx.DateTime.Format().
    Returns:
        A string representation of the JDN based on format.
    '''
    ddt.SetJDN(JDN+0.00000001) # small number added, to fix rounding error
    if format == 'time':
        return ddt.Format("%T")
    if format == 'date':
        return ddt.Format("%m/%d/%Y")
    # to be flexible...
    return ddt.Format(format)

def get_all_experiments(dbcur, dbpath, dbid):
    '''
    Return a dictionary whos keys are expe_ids and 
        values are Experiment objects for all expe_ids
        in a database.
    '''
    dbcur.execute("SELECT expe_id FROM experiment")
    expe_ids = [row[0] for row in dbcur]
    all_exps = {}
    for expe_id in expe_ids:
        all_exps[expe_id] = Experiment(expe_id, dbcur, dbpath=dbpath,
                                       dbid=dbid)
    return all_exps
    
def build_experiment_tree_info(dbcur, dbpath, dbid):
    '''
    Generate a dictionary of:
        = return_dict[spec_id][sess_id] = list_of_Experiment_objects
    Only add specimins/sessions to the list if they have a corresponding
        experiment in the database.
    '''
    experiments = get_all_experiments(dbcur, dbpath, dbid)
    # make a dictionary factory to setup the structure specified in the
    #   above docstring.
    return_dict = defaultdict(lambda :defaultdict(list) )
    # find out what specimins/sessions go with these experiments.
    for experiment in experiments.values():
        spec_id = experiment.spec_id
        sess_id = experiment.sess_id
        return_dict[spec_id][sess_id].append(experiment)

    return experiments, return_dict

class Bunch():
    '''
    this is just a struct to hold data
    '''
    def __init__(self):
        pass

class Experiment():
    '''
    This class holds all information from the database about a single experiment.
        The variables themselves will poll the database to determine their values.
    '''
    def __init__(self, expe_id, dbcur, dbpath=None, dbid=None):
        '''
        Only the expe_id and a database cursor is needed, all other 
          variables will be dynamically determined
          when they are asked for.
        '''
        if dbpath is None:
            dbpath = os.path.join(os.path.sep,
                                  'experiment_data',
                                  'stored_data',
                                  'database.sqlite')
            
            if not os.path.isfile(dbpath):
                current_path = os.path.split(__file__)[0]
                dbpath = os.path.join(current_path, 'database.sqlite')

        if dbid is None:
            id_file = os.path.join(os.path.split(dbpath)[0],'database.id')
            with open(id_file) as file:
                dbid = file.readline()[:-1] # discard '\n' from end.

        if dbcur is None:
            dbcon = sqlite3.connect(dbpath)
            dbcur = dbcon.cursor()

        self.dbpath = dbpath
        self.dbcur = dbcur
        self.dbid  = dbid

        dbcur.execute("SELECT expe_id FROM experiment WHERE expe_id==?",
                      (expe_id,))

        if dbcur.fetchone() is None:
            raise RuntimeError(
                "No experiment with expe_id=%d in database named '%s'" %
                (expe_id, dbid))
        self.expe_id = expe_id

        # set variables for wether or not we've gotten info from the db
        self._got_expe_info    = False
        self._got_sess_info    = False
        self._got_rf_info      = False
        self._got_eye_cup_info = False
        self._got_elec_info    = False
        self._got_phar_info    = False
        self._got_sett_info    = False
        self._got_spec_info    = False
        self._got_data_info    = False
        self._got_eye_cup      = False
        self._got_rf           = False
        # set variables for if we've loaded in data or generated frames.
        self._data        = None
        self._frame_times = None

    def __hash__(self):
        return hash(self.expe_id)

    @classmethod
    def from_default_db(cls, expe_id):
        default_db_path = '/experiment_data/stored_data/database.sqlite'
        dbcon = sqlite3.connect(default_db_path)
        dbcur = dbcon.cursor()
        return Experiment(expe_id, dbcur=dbcur, dbpath=default_db_path)

    @classmethod
    def with_spec_id(cls, spec_id, dbcur, **kwargs):
        '''
        This returns a random experiment object with the spec_id 
            specified, or None if no experiments exist with this
            spec_id.
        '''
        # find all experiments with the given spec_id
        dbcur.execute(''' SELECT expe_id FROM experiment INNER JOIN session
                          ON experiment.sess_id==session.sess_id WHERE 
                          spec_id==? 
                      ''', (spec_id,))
        for row in dbcur:
            return Experiment(row[0], dbcur, **kwargs)
        

    @classmethod
    def with_sess_id(cls, sess_id, dbcur, **kwargs):
        '''
        This returns a random experiment object with the sess_id 
            specified, or None if no experiments exist with this
            sess_id.
        '''
        # find all experiments with the given sess_id
        dbcur.execute(''' SELECT expe_id FROM experiment WHERE 
                          sess_id==? 
                      ''', (sess_id,))
        for row in dbcur:
            return Experiment(row[0], dbcur, **kwargs)

    # ---- DATA FILES INFO ----
    def _get_data_info(self):
        '''
        This function finds values for
            _data_path
            _frame_times_path
            _eye_cup_image_path
            _rf_image_path
            _chalk_image_paths
        '''
        self._data_path = ""
        self._frame_times_path = ""
        self._eye_cup_image_path = ""
        self._rf_image_path = ""
        self._chalk_image_paths = [""]

        import os
        # -- _data_path and _frame_times_path --
        dbpath = self.dbpath 
        # start with base of dbpath then go into session folder
        data_path = os.path.split(dbpath)[0]
        data_path = os.path.join(data_path, 'session_%d' % self.sess_id)
        data_path = os.path.join(data_path, 'experiment_%d' % self.expe_id)
        try:
            data_files = os.listdir(data_path)
        except OSError as ose:
            print str(ose)
            data_files = []
        self._frame_times_path = None # very old experiments didn't save frame times
        for data_file in data_files:
            if not data_file.endswith('.cPickle'): 
                self._data_path = os.path.join(data_path, data_file)
            elif data_file.endswith('times.cPickle'):
                self._frame_times_path = os.path.join(data_path, data_file)

        # -- _rf_image_path  and _chalk_image_paths --
        # start with base of dbpath then go into receptive_field_images folder
        rf_path = os.path.split(dbpath)[0] 
        rf_path = os.path.join(rf_path, 'receptive_field_images')
        rf_path = os.path.join(rf_path, '%s' % self.rf_id)
        self._chalk_image_paths = []
        try:
            rf_files = os.listdir(rf_path)
        except OSError as ose:
            rf_files = []
        for rf_file in rf_files:
            if 'chalk' in rf_file:
                self._chalk_image_paths.append( os.path.join(rf_path, rf_file) )
            elif 'rf' in rf_file:
                self._rf_image_path = os.path.join(rf_path, rf_file)

        # -- _eye_cup_image_path --
        # start with base of dbpath then go into eye_cup_images folder
        eye_cup_path = os.path.split(dbpath)[0] 
        eye_cup_path = os.path.join(eye_cup_path, 'eye_cup_images')
        eye_cup_path = os.path.join(eye_cup_path, '%s' % self.eyec_id)
        try:
            eye_cup_files = os.listdir(eye_cup_path)
        except OSError as ose:
            eye_cup_files = []
        if eye_cup_files:
            eye_cup_file = eye_cup_files[0]
            self._eye_cup_image_path = os.path.join(eye_cup_path, eye_cup_file)

        self._got_data_info = True
    @property
    def data_path(self):
        if not self._got_data_info:
            self._get_data_info()
        return self._data_path
    @property
    def frame_times_path(self):
        if not self._got_data_info:
            self._get_data_info()
        return self._frame_times_path
    @property
    def eye_cup_image_path(self):
        if not self._got_data_info:
            self._get_data_info()
        return self._eye_cup_image_path
    @property
    def rf_image_path(self):
        if not self._got_data_info:
            self._get_data_info()
        return self._rf_image_path
    @property
    def chalk_image_paths(self):
        if not self._got_data_info:
            self._get_data_info()
        return self._chalk_image_paths
    @property
    def data(self, reading_function=read_wessel_text_file):
        if self._data is None:
            self._data = reading_function(self.data_path)
        return self._data
    @property
    def frame_times(self):
        if self._frame_times is None:
            with open(self.frame_times_path) as ofile:
                self._frame_times = cPickle.load(ofile)
        return self._frame_times

        

    # ---- SPECIMIN INFO ----
    def _get_spec_info(self):
        '''
        This function finds values for
            _abnormalities
            _begin_time
            _end_time
            _date
            _spec_note
            _which_eye_was_prepared
        '''
        self.dbcur.execute("""SELECT abnormalities, begin_time, end_time,
                                     date, note, wewp
                              FROM specimin WHERE spec_id=?""", (self.spec_id,))
        results = self.dbcur.fetchone()
        self._abnormalities          = results[0]
        self._begin_time             = results[1]
        self._begin_time_as_string   = format_JDN(results[1], 'time')
        self._end_time               = results[2]
        self._end_time_as_string     = format_JDN(results[2], 'time')
        self._date                   = results[3]
        self._date_as_string         = format_JDN(results[3], 'date')
        self._spec_note              = results[4]
        self._which_eye_was_prepared = results[5]
        self._got_spec_info = True
    @property
    def abnormalities(self):
        if not self._got_spec_info:
            self._get_spec_info()
        return self._abnormalities
    @property
    def begin_time(self):
        if not self._got_spec_info:
            self._get_spec_info()
        return self._begin_time
    @property
    def begin_time_as_string(self):
        if not self._got_spec_info:
            self._get_spec_info()
        return self._begin_time_as_string
    @property
    def end_time(self):
        if not self._got_spec_info:
            self._get_spec_info()
        return self._end_time
    @property
    def end_time_as_string(self):
        if not self._got_spec_info:
            self._get_spec_info()
        return self._end_time_as_string
    @property
    def date(self):
        if not self._got_spec_info:
            self._get_spec_info()
        return self._date
    @property
    def date_as_string(self):
        if not self._got_spec_info:
            self._get_spec_info()
        return self._date_as_string
    @property
    def spec_note(self):
        if not self._got_spec_info:
            self._get_spec_info()
        return self._spec_note
    @property
    def which_eye_was_prepared(self):
        if not self._got_spec_info:
            self._get_spec_info()
        return self._which_eye_was_prepared

    # ---- STIMULUS SETTINGS INFO ----
    def _get_sett_info(self):
        '''
        This function finds values for
            _stim_type
            _stim_svn_revision
            _stim_description
            _settings
            _settings[i].name
            _settings[i].description
            _settings[i].value
        '''
        self.dbcur.execute("""SELECT type, stimulus.description, svn_revision, name, 
                                     setting.description, value, sv_id 
                              FROM stimulus 
                              INNER JOIN setting ON stimulus.stim_id==setting.stim_id 
                              INNER JOIN setting_value         
                                         ON setting.sett_id==setting_value.sett_id 
                              WHERE expe_id==? ORDER BY sv_id""", (self.expe_id,))

        setting_values       = List_dict() 
        setting_names        = [] 
        setting_descriptions = []
        
        for row in self.dbcur:
            stim_name, stim_description, svn_revision, \
                setting_name, setting_description, setting_value, sv_id = row
            setting_values[setting_name] = setting_value
            if setting_name not in setting_names:
                setting_names.append(setting_name)
                setting_descriptions.append(setting_description)

        self._stim_type         = stim_name
        self._stim_svn_revision = svn_revision
        self._stim_description  = stim_description
        self._settings = []
        for sn, sd in zip(setting_names,setting_descriptions):
            self._settings.append(Bunch())
            self._settings[-1].name        = sn
            self._settings[-1].description = sd
            self._settings[-1].value       = setting_values[sn]
        self._got_sett_info = True
    @property
    def stim_type(self):
        if not self._got_sett_info:
            self._get_sett_info()
        return self._stim_type
    @property
    def stim_svn_revision(self):
        if not self._got_sett_info:
            self._get_sett_info()
        return self._stim_svn_revision
    @property
    def stim_description(self):
        if not self._got_sett_info:
            self._get_sett_info()
        return self._stim_description
    @property
    def settings(self):
        if not self._got_sett_info:
            self._get_sett_info()
        return self._settings

    # ---- PHARMACOLOGY INFO ----
    def _get_phar_info(self):
        '''
        This function finds values for
            _drugs
            _drugs[i].concentration
            _drugs[i].location
            _drugs[i].name
            _drugs[i].note
            _drugs[i].time_applied
        '''
        self.dbcur.execute("""SELECT concentration, location, name, note,
                                     time_applied
                              FROM pharmacology WHERE sess_id=?""", (self.sess_id,))
        self._drugs = []
        for row in self.dbcur:
            self._drugs.append(Bunch())
            self._drugs[-1].concentration = row[0]
            self._drugs[-1].location      = row[1]
            self._drugs[-1].name          = row[2]
            self._drugs[-1].note          = row[3]
            self._drugs[-1].time_applied  = row[4]
            self._drugs[-1].time_applied_as_string = format_JDN(row[4],'time')
        self._got_phar_info = True
    @property
    def drugs(self):
        if not self._got_phar_info:
            self._get_phar_info()
        return self._drugs

    # ---- ELECTRODE INFO ----
    def _get_elec_info(self):
        '''
        This function finds values for
            _electrodes
            _electrodes[i].resistance
            _electrodes[i].depth
            _electrodes[i].location
            _electrodes[i].note
            _electrodes[i].trodes
            _electrodes[i].type
        '''
        self.dbcur.execute("""SELECT resistance, depth, location, note,
                                     trodes, type
                              FROM electrode WHERE sess_id=?""", (self.sess_id,))
        self._electrodes = []
        for row in self.dbcur:
            self._electrodes.append(Bunch())
            self._electrodes[-1].resistance = row[0]
            self._electrodes[-1].depth      = row[1]
            self._electrodes[-1].location   = row[2]
            self._electrodes[-1].note       = row[3]
            self._electrodes[-1].trodes     = row[4]
            self._electrodes[-1].type       = row[5]
        self._got_elec_info = True
    @property
    def electrodes(self):
        if not self._got_elec_info:
            self._get_elec_info()
        return self._electrodes

    # ---- EYE_CUP ----
    def _get_eye_cup_object(self):
        '''
        Construct an eye_cup object for this experiment.
        '''
        streak = (self.streak_pos[0], self.streak_pos[1], self.streak_ang)
        cup    = (self.cup_pos[0], self.cup_pos[1], self.cup_diameter)
        nerve  = (self.nerve_pos[0], self.nerve_pos[1], self.nerve_diameter)
        self._eye_cup = Eye_cup(streak, cup, nerve)
        self._got_eye_cup = True
    @property
    def eye_cup(self):
        if not self._got_eye_cup:
            self._get_eye_cup_object()
        return self._eye_cup

    # ---- EYE_CUP INFO ----
    def _get_eye_cup_info(self):
        '''
        This function finds values for
            _cup_diameter
            _cup_pos
            _nerve_diameter
            _nerve_pos
            _streak_ang
            _streak_pos
        '''
        self.dbcur.execute("""SELECT cup_diameter, cup_x, cup_y, nerve_diameter,
                                     nerve_x, nerve_y, streak_ang, streak_x, streak_y
                              FROM eye_cup WHERE eyec_id=?""", (self.eyec_id,))
        results = self.dbcur.fetchone()
        self._cup_diameter   = results[0]
        self._cup_pos        = results[1:3]      
        self._nerve_diameter = results[3]
        self._nerve_pos      = results[4:6]
        self._streak_ang     = results[6]
        self._streak_pos     = results[7:9]
        self._got_eye_cup_info = True
    @property
    def cup_diameter(self):
        if not self._got_eye_cup_info:
            self._get_eye_cup_info()
        return self._cup_diameter
    @property
    def cup_pos(self):
        if not self._got_eye_cup_info:
            self._get_eye_cup_info()
        return self._cup_pos
    @property
    def nerve_diameter(self):
        if not self._got_eye_cup_info:
            self._get_eye_cup_info()
        return self._nerve_diameter
    @property
    def nerve_pos(self):
        if not self._got_eye_cup_info:
            self._get_eye_cup_info()
        return self._nerve_pos
    @property
    def streak_ang(self):
        if not self._got_eye_cup_info:
            self._get_eye_cup_info()
        return self._streak_ang
    @property
    def streak_pos(self):
        if not self._got_eye_cup_info:
            self._get_eye_cup_info()
        return self._streak_pos
        
    # ---- SESS INFO ----
    def _get_sess_info(self):
        '''
        This function finds values for
            _eyec_id
            _sess_note
            _spec_id
            _sess_svn_revision
        '''
        self.dbcur.execute("""SELECT eyec_id, note, spec_id, svn_revision 
                              FROM session WHERE sess_id=?""", (self.sess_id,))
        results = self.dbcur.fetchone()
        self._eyec_id           = results[0]
        self._sess_note         = results[1]
        self._spec_id           = results[2]
        self._sess_svn_revision = results[3]
        self._got_sess_info = True
    @property
    def eyec_id(self):
        if not self._got_sess_info:
            self._get_sess_info()
        return self._eyec_id
    @property
    def sess_note(self):
        if not self._got_sess_info:
            self._get_sess_info()
        return self._sess_note
    @property
    def spec_id(self):
        if not self._got_sess_info:
            self._get_sess_info()
        return self._spec_id
    @property
    def sess_svn_revision(self):
        if not self._got_sess_info:
            self._get_sess_info()
        return self._sess_svn_revision

    # ---- RF ----
    def _get_rf_object(self):
        '''
        Construct an eye_cup object for this experiment.
        '''
        self._rf = RF(self.rf_center, self.rf_size, self.rf_ori)
        self._got_rf = True
    @property
    def rf(self):
        if not self._got_rf:
            self._get_rf_object()
        return self._rf

    # ---- RF INFO ----
    def _get_rf_info(self):
        '''
        This function finds values for
            _rf_note
            _rf_center
            _rf_ori
            _rf_size
        '''
        self.dbcur.execute("""SELECT note, center_x, center_y, ori, size_x, size_y 
                              FROM rf WHERE rf_id=?""", (self.rf_id,))
        results = self.dbcur.fetchone()
        self._rf_note = results[0]
        self._rf_center = results[1:3]
        self._rf_ori    = results[3]
        self._rf_size   = results[4:6]
        self._got_rf_info = True
    @property
    def rf_note(self):
        if not self._got_rf_info:
            self._get_rf_info()
        return self._rf_note
    @property
    def rf_center(self):
        if not self._got_rf_info:
            self._get_rf_info()
        return self._rf_center
    @property
    def rf_ori(self):
        if not self._got_rf_info:
            self._get_rf_info()
        return self._rf_ori
    @property
    def rf_size(self):
        if not self._got_rf_info:
            self._get_rf_info()
        return self._rf_size
    

    # ---- EXPE INFO ----
    def _get_expe_info(self):
        '''
        This function finds values for
            _expe_time
            _expe_note
            _rf_id
            _sess_id
            _bg_shade
        '''
        self.dbcur.execute("""SELECT rf_id, note, sess_id, time, bg_shade FROM experiment
                         WHERE expe_id=?""", (self.expe_id,))
        results = self.dbcur.fetchone()
        self._rf_id               = results[0]
        self._expe_note           = results[1]
        self._sess_id             = results[2]
        self._expe_time           = results[3]
        self._expe_time_as_string = format_JDN(results[3], 'time')
        self._bg_shade            = results[4]
        self._got_expe_info = True
    @property
    def expe_time(self):
        if not self._got_expe_info:
            self._get_expe_info()
        return self._expe_time
    @property
    def expe_time_as_string(self):
        if not self._got_expe_info:
            self._get_expe_info()
        return self._expe_time_as_string
    @property
    def expe_note(self):
        if not self._got_expe_info:
            self._get_expe_info()
        return self._expe_note
    @property
    def rf_id(self):
        if not self._got_expe_info:
            self._get_expe_info()
        return self._rf_id
    @property
    def sess_id(self):
        if not self._got_expe_info:
            self._get_expe_info()
        return self._sess_id
    @property
    def bg_shade(self):
        if not self._got_expe_info:
            self._get_expe_info()
        return self._bg_shade


    @property
    def spec_info_string(self):
        # ---- specimin info ----
        info_string  = "    Date: %s\n    Surgery Begin Time: %s\n    End Time: %s\n" % (self.date_as_string, self.begin_time_as_string, self.end_time_as_string)
        info_string += "    Eye Prepared: %s\n    Eye Cup Abnormalities: %s\n" % (self.which_eye_was_prepared, self.abnormalities)
        notes_string = "    Specimin Notes: %s\n" % self.spec_note
        info_string += string_wrapper(notes_string)

        return info_string

    @property
    def sess_info_string(self):
        # ---- session info ----
        info_string  = "    Subversion Revision: %d\n" % self.sess_svn_revision
        info_string += "    Session Notes: %s \n" % self.sess_note
        # ==========---- eye_cup ----
        info_string += "    Eye Cup Info:\n"
        info_string += "        -- Cup Center:    %d, %d (pixels from center of screen)\n" % self.cup_pos
        info_string += "        -- Cup Size:      %d     (DIAMETER of eyecup in pixels)\n" % self.cup_diameter
        info_string += "        -- Optic Nerve:   %d, %d   Diameter: %d\n" % (self.nerve_pos[0], self.nerve_pos[1], self.nerve_diameter)
        info_string += "        -- Visual Streak: %d, %d   Angle: %d (degrees clockwise from +x)\n " % (self.streak_pos[0], self.streak_pos[1], self.streak_ang)
        # ==========---- receptive field ----
        info_string += "    Receptive Field Info:\n"
        info_string += "        -- Center: %d, %d (pixels from center of screen)\n" % self.rf_center
        info_string += "        -- Size:   %d, %d (magnitude of rf_x or rf_y unit in pixels)\n" % self.rf_size
        info_string += "        -- Orientation: %d (degrees clockwise from +x)\n" % self.rf_ori
        note_string = "        -- Note: %s\n" % self.rf_note
        info_string += string_wrapper(note_string)
        # ==========---- electrodes ----
        for electrode in self.electrodes:
            info_string += "    Electrode Info:\n"
            info_string += "        -- Type: %s   Number of trodes: %d   Resistance: %d (k Ohms)\n" % (electrode.type, electrode.trodes, electrode.resistance)
            # need a fix for empty depth field
            try: 
                depth = int(electrode.depth)
                info_string += "        -- Location: %s   Depth: %d (um)   Note: %s \n" % (electrode.location, int(electrode.depth), electrode.note)
            except ValueError: 
                if electrode.depth == "":
                    depth = "Field left blank"
                else:
                    depth = electrode.depth
                info_string += "        -- Location: %s   Depth: %s   Note: %s \n" % (electrode.location, depth, electrode.note)
        # ==========---- pharamcology ----
        for drug in self.drugs:
            info_string += "    Pharmacology Info:\n"
            info_string += "        -- Name: %s   Concentration: %f (mM)  Location: %s\n" % (drug.name, drug.concentration, drug.location)
            info_string += "        -- Time Applied: %s    Note: %s \n" % (drug.time_applied_as_string, drug.note)
        return info_string

    @property
    def expe_info_string(self):
        # ---- experiment info ----
        info_string  = "     Time: %s    Note: %s\n" % (self.expe_time_as_string, self.expe_note)
        # ==========---- stimulus ----
        info_string += "     Stimulus Info:\n"
        info_string += "         -- Background Shade: %f\n" % self.bg_shade
        info_string += "         -- Stimulus Name: %s \n" % self.stim_type
        info_string += "         -- Stimulus Description: %s \n" % self.stim_description
        info_string += "         -- Subversion Revision: %d\n" % self.stim_svn_revision
        info_string += "     Stimulus Settings:\n"
        for s in self.settings:
            setting_string = "         -- %s: %s (%s)\n" % (s.name, str(s.value), s.description)
            info_string += string_wrapper(setting_string)
        return info_string

    @property
    def full_info_string(self):
        info_string =  "        Data File %d Information Sheet\n" % self.expe_id
        info_string += " Specimin Info:\n"
        info_string += self.spec_info_string 
        info_string += " Session Info:\n"
        info_string += self.sess_info_string
        info_string += " Experiment Info:\n"
        info_string += self.expe_info_string
        return info_string


    def write_info_sheet(self, full_path):
        '''
        Write out the information about the data from the database to a text file.
            Specimin
            Session
            Experiment
            Stimulus
        Will be written to fullpath.
        '''
        sheet_string = self.full_info_string

        # finally, write the string to the file.
        file = open(full_path, 'w')
        file.write(sheet_string)
        file.close()

    def __str__(self):
        return "Experiment #%d from %s" % (self.expe_id, self.dbid)

    def run(self, **kwargs):
        # import the stimulus
        exec("import projects.electrophysiology.stimuli.%s as stimulus" % 
             self.stim_type )

        values = []
        for s in self.settings:
            values.append(s.value)

        if 'eye_cup' not in kwargs.keys():
            kwargs['eye_cup'] = self.eye_cup
        if 'rf' not in kwargs.keys():
            kwargs['rf'] = self.rf

        # set the rf and eye_cup for background for drawing.
        self.eye_cup.update_bg_shade(self.bg_shade)
        self.rf.update_bg_shade(self.bg_shade)

        return stimulus(*values, **kwargs)


    @property
    def export_file_prefix(self):
        return str(self.expe_id) + '-' + self.dbid.replace(' ','_')

    def get_export_commands(self, export_path):
        commands = []
        file_prefix = self.export_file_prefix
        # [[data]]
        old_path = self.data_path
        new_path = os.path.join(export_path, 
                                '%(file_prefix)s-data' % locals())
        if os.path.isfile(old_path):
            commands.append('cp "%s"  "%s"'  % (self.data_path, new_path))
        # [[frame times]]
        old_path = self.frame_times_path
        new_path = os.path.join(export_path,
                               '%(file_prefix)s-frame_times.cPickle' %
                               locals())
        # very old experiments didn't save frame times.
        if old_path is not None and os.path.isfile(old_path): 
            commands.append('cp "%s"  "%s"'  % (old_path, new_path))

        # [[rf]]
        old_path = self.rf_image_path
        file_suffix = os.path.splitext(old_path)[-1]
        new_path = os.path.join(export_path,
                                '%(file_prefix)s-rf%(file_suffix)s' %
                                locals())
        if os.path.isfile(old_path):
            commands.append('cp "%s"  "%s"'  % (old_path, new_path))

        # [[eye_cup]]
        old_path = self.eye_cup_image_path
        file_suffix = os.path.splitext(old_path)[-1]
        new_path = os.path.join(export_path, 
                                '%(file_prefix)s-eye_cup%(file_suffix)s' %
                                locals())
        if os.path.isfile(old_path):
            commands.append('cp "%s"  "%s"'  % (old_path, new_path))

        # [[chalk]]
        chalk_count = 0
        for chalk_path in self.chalk_image_paths:
            chalk_count += 1
            file_suffix = os.path.splitext(chalk_path)[-1]
            new_path = os.path.join(export_path,
                  '%(file_prefix)s-chalk_%(chalk_count)d%(file_suffix)s' %
                  locals())
            if os.path.isfile(chalk_path):
                commands.append('cp "%s"  "%s"'  % (chalk_path, new_path))
        return commands


    def export(self, export_path, **kwargs):
        info_sheet_fullpath = os.path.join(export_path, 
                                           '%s-info_sheet.txt' 
                                            % self.export_file_prefix)
        self.write_info_sheet( info_sheet_fullpath )
        for command in self.get_export_commands(export_path):
            os.system(command)

        if 'save_frames' in kwargs.keys():
            if kwargs['save_frames']:
                return_values = self.run(**kwargs)
                frames_times, frames = return_values
                frames = format_frames(frames)
                frames_filename = os.path.join(export_path,
                                               '%s-frames.cPickle' %
                                               self.export_file_prefix)
                with open(frames_filename, 'w') as file:
                    cPickle.dump(frames, file, protocol=-1)

    # convienence functions for writing data analysis scripts using Experiment
    #     objects
    def get_frames(self, save_frames_factor, save_frames_size, 
                         display_saved_frames=False, 
                         eye_cup_opacity=0.0, 
                         rf_opacity=0.0,
                         greyscale=True):
        # update the opacity of the rf and eye_cup
        self.eye_cup.update_opacity(eye_cup_opacity)
        self.rf.update_opacity(rf_opacity)
        # construct and return the frames
        frame_times, frames = self.run(eye_cup=self.eye_cup,
                                       rf=self.rf,
                                       save_frames=True, 
                                       save_frames_factor=save_frames_factor, 
                                       save_frames_size=save_frames_size)
        return format_frames(frames, greyscale=greyscale)
        

    def get_setting(self, setting_name):
        for setting in self.settings:
            if setting.name == setting_name:
                return setting.value
        print "Setting named: '%s' does not exist for this experiment." % setting_name
        return None
    

def string_wrapper(string, max_string_length=79, 
                           alignment_character=":",
                           alignment_offset=2):
    '''
    This function will add newlines between words and indent subsequent
        lines so that they line up with the first alignment_character 
        on the first line.
    '''
    if len(string) <= max_string_length+2: # +2 for '\n' characters.
        return string

    # find the number of spaces to add if we break.
    number_of_spaces = string.index(alignment_character)

    spaces = '                                                            '
    subsequent_indent = spaces[:number_of_spaces + alignment_offset]
    
    wrapped_lines = textwrap.wrap(string, subsequent_indent=subsequent_indent,
                                          width=max_string_length)

    wrapped_string = ''
    for line in wrapped_lines:
        wrapped_string += line + '\n'

    return wrapped_string

